
/****************************************************************************/
/*Copyright (c) 2011, Florent DEVILLE.                                      */
/*All rights reserved.                                                      */
/*                                                                          */
/*Redistribution and use in source and binary forms, with or without        */
/*modification, are permitted provided that the following conditions        */
/*are met:                                                                  */
/*                                                                          */
/* - Redistributions of source code must retain the above copyright         */
/*notice, this list of conditions and the following disclaimer.             */
/* - Redistributions in binary form must reproduce the above                */
/*copyright notice, this list of conditions and the following               */
/*disclaimer in the documentation and/or other materials provided           */
/*with the distribution.                                                    */
/* - The names of its contributors cannot be used to endorse or promote     */
/*products derived from this software without specific prior written        */
/*permission.                                                               */
/* - The source code cannot be used for commercial purposes without         */ 
/*its contributors' permission.                                             */
/*                                                                          */
/*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       */
/*"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         */
/*LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         */
/*FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            */
/*COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       */
/*INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,      */
/*BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          */
/*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER          */
/*CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT        */
/*LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN         */
/*ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/*POSSIBILITY OF SUCH DAMAGE.                                               */
/****************************************************************************/

#include "CRayTracer.h"
#include "CSimpleScene.h"
#include "IPrimitive.h"
#include <math.h>
#include <iostream>
#include "CMaterial.h"

//use to get access to lights function to calculate illumination.
#include "ILight.h"

CRayTracer* CRayTracer::m_instance = 0;

//generate the primary ray for a specific pixel
void CRayTracer::generateRay(int x, int y, CRay& ray)const
{
	//generate the primary ray starting from the camera and going to the scene
	ray.setDirection(CVector3f(
		m_precompWidth * (x - m_pixelWidth/2.f),
		m_precompHeight * (y - m_pixelHeight/2.f),
		m_fov));

	//normalize the direction
	ray.getDirection().normalize();

	//if left_handed coordinate system, reverse the z axis
	if(_handedness == LEFT_HANDED)
		ray.getDirection().z = - ray.getDirection().z;
}

void CRayTracer::generateOverSamplingRay(int x, int y, CRay* ray)const
{
	//create the first ray
	CVector3f offset(
		m_precompWidth * (x - m_pixelWidth/2.f),
		m_precompHeight * (y - m_pixelHeight/2.f),
		m_fov);

	//if left_handed coordinate system, reverse the z axis
	if(_handedness == LEFT_HANDED)
		offset.z = - offset.z;

	//create all the other ray
	for(int i = 1; i<m_overSamplingLevel*m_overSamplingLevel; i++)
	{
		//calculate the offset
		int tempX = i % m_overSamplingLevel;
		int tempY = i / m_overSamplingLevel;

		//put the offset in a vector
		CVector3f newOffset = offset + CVector3f(tempX * m_precompPixelOffsetX, tempY * m_precompPixelOffsetY, 0);
		newOffset.normalize();

		//set the ray's direction
		ray[i].setDirection(newOffset);
	}

	//normalize the first ray (has to be done here)!!!
	offset.normalize();
	ray[0].setDirection(offset);
}

void CRayTracer::computeColor(const CRay& ray, CColor& color, int /*reflection*/, float refractionIndex)const
{

	//distance between the ray's origin and the primitive
	float dist = -1;

	//intersection point between the ray and the primitive
	CVector3f center;

	//the primitive which intersects with the ray
	IPrimitive* p = 0;

	//calculate intersection
	RAYTRACER.PIPELINEgetPrimitive(ray, dist, center, &p);
	if(dist == -1)
		return;

	//calculate the raw color of the pixel (from texture, material, etc)
	CColor rawPixelColor;
	RAYTRACER.PIPELINEgetRawColor(*p, center, rawPixelColor);

	//calculate the normal (primitive + bump)
	CVector3f normal;
	RAYTRACER.PIPELINEgetNormal(*p, ray, center, dist, normal);
	
	//calculate the color using light
	RAYTRACER.PIPELINEcomputeLight(ray.getDirection(), center, normal, *p, rawPixelColor, color);

	//calculate transparency
	//float primitiveRefractionIndex = (float)p->getMaterial()->m_refractionIndex;
	//RAYTRACER.PIPELINEcomputeTransparency(ray, dist, normal, refractionIndex, primitiveRefractionIndex, color, color);

	return;

	////transparency
	//if(p->getMaterial()->m_transparancyCoef != 0 )
	//{
	//	color = color * (1 - p->getMaterial()->m_transparancyCoef);

	//	//get the refracted vector
	//	CVector3f newDirection = getRefractionVector(normal, ray.getDirection(), 
	//		ray.getOrigin()+ray.getDirection()*dist, refractionIndex, p->getMaterial()->m_refractionIndex);

	//	//get the fresnel coefficient
	//	float reflectanceValue = reflectance(normal, ray.getDirection(), refractionIndex, 
	//		p->getMaterial()->m_refractionIndex);
	//	float refractionValue = 1 - reflectanceValue;

	//	CColor secondColor;
	//	if(refractionValue > 0.001)
	//	{
	//		//get the transmitted ray
	//		CRay transmittedRay(ray.getOrigin()+ray.getDirection()*(dist + 0.01f), newDirection);
	//		CColor transmittedColor(0, 0, 0);
	//		computeColor(transmittedRay, transmittedColor, reflection, 
	//			(float)p->getMaterial()->m_refractionIndex, !inside);
	//		secondColor += transmittedColor * refractionValue;
	//	}

	//	if(reflectanceValue > 0.001 && inside == false)
	//	{
	//		CRay reflectedRay(ray.getOrigin()+ray.getDirection()*(dist + 0.01f), 
	//			getReflectionVector(normal, ray.getDirection()));
	//		CColor reflectedColor(0, 0, 0);
	//		computeColor(reflectedRay, reflectedColor, reflection, (float)p->getMaterial()->m_refractionIndex, inside);
	//		secondColor += reflectedColor * reflectanceValue;
	//	}

	//	color += secondColor * p->getMaterial()->m_transparancyCoef;
	//}

	//if(p->getMaterial()->m_reflect == CColor(1, 1, 1))//mirror
	//	color = CColor(0, 0, 0);

	////calculate reflection
	//if(reflection>0 && p->getMaterial()->m_reflect != CColor(0, 0, 0))
	//{
	//	//get the new ray
	//	CPoint3f newOrigin = ray.getOrigin()+(ray.getDirection()*dist);
	//	CVector3f newDirection = getReflectionVector(normal, ray.getDirection());
	//	newOrigin = newOrigin + (newDirection * 0.1f);

	//	//create the reflected ray
	//	CRay secondRay(newOrigin, newDirection);

	//	//new color
	//	CColor secondColor(0, 0, 0);
	//	computeColor(secondRay, secondColor, reflection-1, refractionIndex, inside);

	//	//add the new color.
	//	color += secondColor * p->getMaterial()->m_reflect;

	//}
}

void CRayTracer::updateParameters()
{
	m_precompWidth = m_width/m_pixelWidth;
	m_precompHeight = m_height/m_pixelHeight;

	m_precompPixelOffsetX = m_precompWidth;
	m_precompPixelOffsetY = m_precompHeight;
}

CRayTracer::CRayTracer():m_origin(0, 0, 0), m_direction(0, 0, 1), m_pixelWidth(800), m_pixelHeight(450),
	m_fov(20), m_height(16), m_width(9), m_overSamplingLevel(NO_OVERSAMPLING), _colorSwapped(false),
	_stopRendering(false), _handedness(LEFT_HANDED)
{
	float m_fov = (3.1416f * 20) / 180;
	m_fov /= 2;
	m_fov = (m_width / 2) / tan(m_fov);

	updateParameters();

}

CRayTracer::CRayTracer(int pixelWidth, int pixelHeight, float width, float height, float fov)
	:m_origin(0, 0, 0), m_direction(0, 0, 1), m_pixelWidth(pixelWidth), m_pixelHeight(pixelHeight),
	m_fov(fov), m_height(height), m_width(width), m_overSamplingLevel(NO_OVERSAMPLING), _handedness(LEFT_HANDED)
{
	//convert fov in rad
	float m_fov = (3.1416f * fov) / 180;
	m_fov /= 2;
	m_fov = (width / 2) / tan(m_fov);

	updateParameters();

}

CRayTracer::~CRayTracer(){}

CRayTracer& CRayTracer::getInstance()
{
	if(m_instance == 0)
		m_instance = new CRayTracer();

	return *m_instance;
}

void CRayTracer::close()
{
	if(m_instance != 0)
	{
		delete m_instance;
		m_instance = 0;
	}
}

//prepare the raytracer for rendering
void CRayTracer::preDraw()
{
	_stopRendering = false;
}

void CRayTracer::draw(char* screenBuffer, int reflection)const
{
	//apply the view matrix to the scene
	SCENE.applyViewMatrix(m_viewMatrix);

	const int nbColors = 3;
	int numberOfRays = m_overSamplingLevel*m_overSamplingLevel;
	CRay* ray = 0;
	ray = new CRay[numberOfRays];

	for(int i = 0; i<numberOfRays; i++)
		ray[i].setOrigin(m_origin);
	
	int numberOfLine=0;
	for(int j = m_pixelHeight-1; j>= 0; j--) //start at the last line j = Y
	{
		for(int i = 0; i<m_pixelWidth; i++) //i = X
		{
//#ifdef _DEBUB
			//START
			//to breakpoint at a specific pixel
			int x = 168;
			int y = 163;
			int yR = m_pixelHeight - y - 1;

			//if(j == yR &&  i == x)
				//int a = 0;
			
			//END
//#endif
			CColor color;

			generateOverSamplingRay(i, j, ray);//generate the rays
			float ratio = 1.f/(numberOfRays);
			for(int k = 0; k<numberOfRays; k++)
			{
				//compute color
				CColor tempColor;

				//calculate the pixel color
				computeColor(ray[k], tempColor, reflection, 1);

				//color average
				color += tempColor * ratio;
			}
			

			//saturate color (between 0 and 1)
			color.saturate();
			//maximize (between 0 and 255)
			color.maximize(255);
			
			//set the color in the picture
			if (_colorSwapped) //GBR
			{
				screenBuffer[nbColors * (numberOfLine*m_pixelWidth + i)] = unsigned char(color.RTVEC4Z);
				screenBuffer[nbColors * (numberOfLine*m_pixelWidth + i)+2] = unsigned char(color.RTVEC4X);
			}
			else //RGB
			{
				screenBuffer[nbColors * (numberOfLine*m_pixelWidth + i)] = unsigned char(color.RTVEC4X);
				screenBuffer[nbColors * (numberOfLine*m_pixelWidth + i)+2] = unsigned char(color.RTVEC4Z);
			}

			screenBuffer[nbColors * (numberOfLine*m_pixelWidth + i)+1] = unsigned char(color.RTVEC4Y);

			//stopped signal
			if (_stopRendering)
			{
				delete[] ray;
				return;
			}
		}
		numberOfLine++;

		//calculate progress
		float percent = (numberOfLine+1)/(float)m_pixelHeight*100;

		//only 2 number after the point
		int t = int(percent*100);
		percent = (float)t/100;
		if(percent>100)
			percent = 100;

		//display
		std::cout<<"\r"<<"PROGRESS : "<<percent<<" %   ";

		/*VISU.preRender();
		VISU.doRender();
		VISU.postRender();*/
		
	}

	delete[] ray;
}

void CRayTracer::setViewer(const CVector3f& eye, const CVector3f& at, const CVector3f& up)
{
	//calculate the viex matrix
	CVector3f zaxis = at - eye; zaxis.normalize(); 

	//if left_handed coordinate system, reverse the z axis
	if(_handedness == LEFT_HANDED)
		zaxis = -zaxis;

	CVector3f xaxis = up.cross(zaxis); xaxis.normalize();
	CVector3f yaxis = zaxis.cross(xaxis); yaxis.normalize();

	m_viewMatrix.identity();

	m_viewMatrix.m_array[0][0] = xaxis.x;
	m_viewMatrix.m_array[0][1] = yaxis.x;
	m_viewMatrix.m_array[0][2] = zaxis.x;

	m_viewMatrix.m_array[1][0] = xaxis.y;
	m_viewMatrix.m_array[1][1] = yaxis.y;
	m_viewMatrix.m_array[1][2] = zaxis.y;

	m_viewMatrix.m_array[2][0] = xaxis.z;
	m_viewMatrix.m_array[2][1] = yaxis.z;
	m_viewMatrix.m_array[2][2] = zaxis.z;

	m_viewMatrix.m_array[3][0] = -xaxis.dot(eye);
	m_viewMatrix.m_array[3][1] = -yaxis.dot(eye);
	m_viewMatrix.m_array[3][2] = -zaxis.dot(eye);	
}

void CRayTracer::setResolution(int pixelWidth, int pixelHeight)
{
	m_pixelWidth = pixelWidth;
	m_pixelHeight = pixelHeight;

	updateParameters();
}

void CRayTracer::setScreen(float ratioWidth, float ratioHeight, float fov)
{
	m_height = ratioHeight;
	m_width = ratioWidth;
	
	//convert fov in rad
	float m_fov = (3.1416f * fov) / 180;
	m_fov /= 2;
	m_fov = (m_width / 2) / tan(m_fov);

	updateParameters();
}

void CRayTracer::setOverSampling(OVERSAMPLING lvl_ovs)
{
	m_overSamplingLevel = lvl_ovs;
	m_precompPixelOffsetX = m_precompWidth / m_overSamplingLevel;
	m_precompPixelOffsetY = m_precompHeight / m_overSamplingLevel;

}

int CRayTracer::getPixelWidth()const{return m_pixelWidth;}
int CRayTracer::getPixelHeight()const{return m_pixelHeight;}
CMatrix CRayTracer::getViewMatrix()const{return m_viewMatrix;}

CVector3f CRayTracer::getRefractionVector(const CVector3f& normal, const CVector3f& incident, const CVector3f& /*hit*/,
										  double n1, double n2)const
{
	const double n = n1 / n2;
	const double cosI = -normal.dot(incident);
	double sinT2 = n * n * (1.0 - cosI * cosI);
	if (sinT2 > 1.0)
		return CVector3f(0, 0, 0);

	//CVector3f T=incident*n+hit*(n*cosI-sqrt(1.0-sinT2));
	CVector3f T=incident*(float)n+normal*((float)n*(float)cosI-sqrt(1.f-(float)sinT2));
    T.normalize();
	return T;
}

/* Return the normalized reflection vector of the incident vector*/
CVector3f CRayTracer::getReflectionVector(const CVector3f& normal, const CVector3f& incident)const
{
	const double cosI = normal.dot(-incident);
	CVector3f R = incident + (normal * (float)cosI * 2);
	R.normalize();
	return R;
}

float CRayTracer::reflectance(const CVector3f& normal, const CVector3f& incident, double n1, double n2)const
{
	double r0 = (n1 - n2) / (n1 + n2);
	r0 *= r0;
	double cosI = -normal.dot(incident);
	if(n1 > n2)
	{
		const double n = n1 / n2;
		const double sinT2 = n * n * (1 - cosI * cosI);
		if(sinT2 > 1) return 1;
		cosI = sqrt(1 - sinT2);
	}
	const double x = 1 - cosI;
	return (float)(r0 + (1 - r0) * x * x * x * x * x); 
} 

inline void CRayTracer::PIPELINEgetPrimitive(const CRay& primaryRay, float& dist, CPoint3f& center, IPrimitive** primitive)
{
	//ask to the scene for the intersection
	int index = SCENE.getFirstCollision(primaryRay, dist);
	if(index<0) // no intersection, so return
		return;
	//get the primitive
	*primitive = SCENE.getPrimitive(index);

	//calculate the intersection point
	center = primaryRay.getOrigin()+primaryRay.getDirection()*dist;
}

inline void CRayTracer::PIPELINEgetRawColor(const IPrimitive& primitive, const CVector3f& center, CColor& output)
{
	//calculate the intersection point (center) into local primitive coordinates.
	CVector3f interLC = center * primitive.getInverseWorldViewMatrix();

	//ask to the material for the diffuse component (the raw clolor of the pixel)
	output = primitive.getMaterial()->getDiffuse(interLC, primitive);
}

inline void CRayTracer::PIPELINEgetNormal(const IPrimitive& primitive, const CRay& primaryRay, const CPoint3f& center, const float& dist, 
	CVector3f& output)
{
	//Calculate normal
	//CNormal3f normal;
	primitive.computeNormal(primaryRay, dist, output);

	//add the bump map
	CVector3f bumpNormal = primitive.getMaterial()->getBumpNormal(center);
	output += bumpNormal;
	output.normalize();
}

inline void CRayTracer::PIPELINEcomputeLight(const CVector3f& direction, const CPoint3f& center, const CVector3f& normal, 
	const IPrimitive& primitive, const CColor& input, CColor& output)
{
	CColor t_color;
	//get material
	CMaterial* caracteristics = primitive.getMaterial();

	//go throught all the lights
	unsigned int nbOfLights = SCENE.getNumberOfLights();
	for(unsigned int lightCount = 0; lightCount < nbOfLights; lightCount ++)
	{
		//get the light
		ILight* ptrLight = SCENE.getLight(lightCount);
	
		//compute the shadow ray
		CRay shadowRay;
		float lightDistance = -1;
		ptrLight->computeShadowRay(center, shadowRay, &lightDistance);
		
		//check if there is something between the object and the light
		if(!SCENE.testCollision(shadowRay, lightDistance)) //no collision
		{
			//get the light color and light direction vector
			CColor lightColor = ptrLight->computeColor(shadowRay, lightDistance);
			CVector3f L = shadowRay.getDirection();

			//LAMBERT
			float LdotN = L.dot(normal); 
			if(LdotN > 0.f)
			{
				//t_color += lightColor * caracteristics->getDiffuse(interLC, p)*LdotN;
				t_color += lightColor * input * LdotN;
				
				//PHONG
				CVector3f lightReflection = L - (normal * 2.0f * LdotN);
				float dot = direction.dot(lightReflection); 
				if(dot > 0.f)
					t_color += lightColor * powf(dot, caracteristics->m_shininess)*caracteristics->getSpecular(center, primitive);

				//BLINN PHONG
				/*CVector3f H = L + view;
				H.normalize();
				float NdotH = normal.dot(H);
				if(NdotH > 0.f)
					t_color += (*i)->getColor()*powf(NdotH, caracteristics.m_shininess)*caracteristics.m_specular;*/
			}
		}
	}
	
	//AMBIENT
	//if(caracteristics->useBumpMapping())
	//	t_color += normal*caracteristics->getAmbient(interLC, primitive)*SCENE.getGlobalAmbientLight();
	//else

	//center is in view coordinates
	CVector3f interLC = center * primitive.getInverseWorldViewMatrix();
	t_color += caracteristics->getAmbient(interLC, primitive)*SCENE.getGlobalAmbientLight();

	//saturate (between 0 and 1)
	t_color.saturate();

	output = t_color;
}

//Pipeline rendering function calculating the transparancy color
inline void CRayTracer::PIPELINEcomputeTransparency(const CRay& incidentRay, float dist,
	const CNormal3f& normal, float incidentRefraction, float reflectedRefraction,
	const CColor& inputColor, CColor& outputColor)
{
	//no transparency
	if (inputColor.RTVEC4W == 1)
	{
		outputColor = inputColor; 
		return;
	}

	//calculate interesection point
	CVector3f center = incidentRay.getOrigin() + incidentRay.getDirection() * dist;
	CVector3f newCenter = incidentRay.getOrigin() + incidentRay.getDirection() * (dist + 0.01f);

	//get the refracted vector
	CVector3f newDirection = getRefractionVector(normal, incidentRay.getDirection(), center, incidentRefraction, reflectedRefraction);

	//reflectance : how much is reflected
	float reflectanceValue = reflectance(normal, incidentRay.getDirection(), 1, 1);

	//refraction : how much goes through
	float refractionValue = 1 - reflectanceValue;


	CColor secondColor;
	if(refractionValue > 0.001) //calculate reflection
	{
		//get the transmitted ray
		CRay transmittedRay(newCenter, newDirection);
		CColor transmittedColor;
		computeColor(transmittedRay, transmittedColor, 0, reflectedRefraction);
		secondColor += transmittedColor * refractionValue;
	}

	//if(reflectanceValue > 0.001) // && !inside
	//{
	//	CRay reflectedRay(newCenter, getReflectionVector(normal, incidentRay.getDirection()));
	//	CColor reflectedColor;
	//	computeColor(reflectedRay, reflectedColor, 0, reflectedRefraction);
	//	secondColor += reflectedColor * reflectanceValue;
	//}

	//calculate final color
	outputColor = inputColor * inputColor.RTVEC4W + secondColor * (1 - inputColor.RTVEC4W);
}

//set the color swapped flag. If true, color order is BGR. If false : RGB
void CRayTracer::setColorSwapped(bool colorSwapped)
{
	_colorSwapped = colorSwapped;
}

//set the stop signal for the rendering loop
void CRayTracer::setStopSignal(bool stop)
{
	_stopRendering = stop;
}

//set the coordinate system (use LEFT_HANDED or RIGHT_HANDED)
void CRayTracer::setCoordinateSystem(bool handedness)
{
	_handedness = handedness;
}